package net.techwatch.fsindex;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import net.techwatch.fsindex.dao.FileSystemDao;
import net.techwatch.fsindex.event.FileSystemEvent;
import net.techwatch.fsindex.event.FileSystemEventHandler;
import net.techwatch.fsindex.listener.FileSystemListener;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.StopWatch;

/**
 * @author wiv
 *
 */
public class FileSystemMonitor implements FileSystemEventHandler, Terminable, FileSystemParser {
	
	private Log log = LogFactory.getLog(FileSystemMonitor.class);
	
	/**
	 * 
	 */
	private List<FileSystemListener> listeners = new ArrayList<FileSystemListener>();
	
	/**
	 * 
	 */
	private FileSystemDao fileSystemDao;
	
	/**
	 * 
	 */
	private ExecutorService service = Executors.newFixedThreadPool(5);
	
	private CountDownLatch semaphore = new CountDownLatch(5);
	
	/**
	 * Default constructor
	 */
	public FileSystemMonitor() {
		// nothing to do
	}
	
	/**
	 * @return the listeners
	 */
	public List<FileSystemListener> getListeners() {
		return listeners;
	}

	/**
	 * @param listeners the listeners to set
	 */
	public void setListeners(List<FileSystemListener> listeners) {
		this.listeners = listeners;
	}

	/**
	 * @param fileSystemDao
	 */
	public void setFileSystemDao(FileSystemDao dataManager) {
		this.fileSystemDao = dataManager;
	}
	
	/**
	 * @return
	 */
	public FileSystemDao getFileSystemDao() {
		return this.fileSystemDao;
	}
	
	/**
	 * @param listener
	 */
	public void addFileSystemListener(FileSystemListener listener) {
		listeners.add(listener);
	}

	/**
	 * @param listener
	 */
	public void removeFileSystemListener(FileSystemListener listener) {
		listeners.remove(listener);
	}
	
	/**
	 * Parse file system and post events when new/update/delete file is encountered. 
	 */
	public void parse() {
		if(log.isDebugEnabled())
			log.debug("Begin synchronization");
		
		StopWatch watch = new StopWatch();
		watch.start();
		
		// use thread to monitor file system
		
		
		FilesChecker filesChecker = new FilesChecker();
		filesChecker.setCountDownLatch(semaphore);
		filesChecker.setFileSystemDataManager(getFileSystemDao());
		filesChecker.setFileSystemMonitor(this);
		filesChecker.setFileSystemObjectList(fileSystemDao.getFiles());
		service.execute(filesChecker);
		
		DirectoriesChecker dirsChecker0 = new DirectoriesChecker();
		dirsChecker0.setCountDownLatch(semaphore);
		dirsChecker0.setFileSystemDataManager(getFileSystemDao());
		dirsChecker0.setFileSystemMonitor(this);
		dirsChecker0.setFileSystemObjectList(fileSystemDao.getDirLevel0());
		service.execute(dirsChecker0);
		
		DirectoriesChecker dirsChecker1 = new DirectoriesChecker();
		dirsChecker1.setCountDownLatch(semaphore);
		dirsChecker1.setFileSystemDataManager(getFileSystemDao());
		dirsChecker1.setFileSystemMonitor(this);
		dirsChecker1.setFileSystemObjectList(fileSystemDao.getDirLevel1());
		service.execute(dirsChecker1);
		
		DirectoriesChecker dirsChecker2 = new DirectoriesChecker();
		dirsChecker2.setCountDownLatch(semaphore);
		dirsChecker2.setFileSystemDataManager(getFileSystemDao());
		dirsChecker2.setFileSystemMonitor(this);
		dirsChecker2.setFileSystemObjectList(fileSystemDao.getDirLevel2());
		service.execute(dirsChecker2);
		
		DirectoriesChecker dirsChecker3 = new DirectoriesChecker();
		dirsChecker3.setCountDownLatch(semaphore);
		dirsChecker3.setFileSystemDataManager(getFileSystemDao());
		dirsChecker3.setFileSystemMonitor(this);
		dirsChecker3.setFileSystemObjectList(fileSystemDao.getDirLevel3());
		service.execute(dirsChecker3);
		
		// wait for completion
		try {
			semaphore.await();
		} catch(InterruptedException e) {
			service.shutdown();
		}
		
		watch.stop();
		if(log.isInfoEnabled())
			log.info("Synchronization completed in " + watch.toString());
	}
	
	/**
	 * @param event
	 */
	public void postDelete(FileSystemEvent event) {
		if(log.isDebugEnabled())
			log.debug("Deletion detected on file " + event.getParentPath() + "/" + event.getName());
		
		for(FileSystemListener listener : listeners)
			listener.onDelete(event);
	}
	
	/**
	 * @param event
	 */
	public void postCreate(FileSystemEvent event) {
		if(log.isDebugEnabled())
			log.debug("Creation detected on file " + event.getParentPath() + "/" + event.getName());
		
		for(FileSystemListener listener : listeners)
			listener.onCreate(event);
	}
	
	/**
	 * @param event
	 */
	public void postUpdate(FileSystemEvent event) {
		if(log.isDebugEnabled())
			log.debug("Update detected on file " + event.getParentPath() + "/" + event.getName());
		
		for(FileSystemListener listener : listeners)
			listener.onUpdate(event);
	}

	/* (non-Javadoc)
	 * @see eu.curia.pex.objectloader.fs.Terminable#terminate()
	 */
	public void terminate() throws Exception {
		// terminate threads contains in this pool
		service.shutdownNow();
		
		// free the semaphore to unblocked the current monitor
		while(semaphore.getCount() > 0)
			semaphore.countDown();
	}

}
